﻿#region Using References

using System;
using System.Diagnostics.Contracts;
using Beaker.OpenCube.Render.Maths;
using Beaker.OpenCube.Render.Tracing;
using Beaker.Services.Tasks;

#endregion

namespace Beaker.OpenCube.Render.Cameras
{
	public sealed class PinholeCamera
		: Camera
	{
		private readonly double _viewPlaneDistance;
		private readonly double _zoomFactor;

		public PinholeCamera( Point3 cameraPosition, Point3 lookAt, Vector3 up, double viewPlaneDistance, double zoomFactor )
			: base( cameraPosition, lookAt, up )
		{
			_viewPlaneDistance = viewPlaneDistance;
			_zoomFactor = zoomFactor;
		}

		[Pure]
		public double ViewPlaneDistance
		{

			get { return _viewPlaneDistance; }
		}

		[Pure]
		public double ZoomFactor
		{

			get { return _zoomFactor; }
		}

		protected override IObservable<IRenderUpdate> RenderCore( Scene scene, ViewPlane viewPlane, ITracer tracer )
		{
			double size = 1d / _zoomFactor * viewPlane.PixelSize;
			double hWidth = viewPlane.PixelWidth / 2d;
			double hHeight = viewPlane.PixelHeight / 2d;

			ITaskCollection<IRenderUpdate> tasks = TaskService.Default.CreateCollection<IRenderUpdate>();

			for( int line = 0; line < viewPlane.PixelHeight; line++ )
			{
				double dLine = line;
				int currentLine = line;

				tasks.Execute(
					() =>
					{
						for( int pixel = 0; pixel < viewPlane.PixelWidth; pixel++ )
						{
							double dPixel = pixel;
							Color pixelColor = Color.Background;


							foreach( Point2 sample in viewPlane.Sampler.GenerateSamplesSquare() )
							{
								double x = size * ( dPixel - hWidth + sample.X - 0.5d );
								double y = size * ( dLine - hHeight + sample.Y - 0.5d );

								Vector3 direction = GetDirection( x, y );
								var ray = new Ray( Position, direction );

								pixelColor += tracer.TraceRay( scene, ray );
							}

							pixelColor /= viewPlane.Sampler.SampleCount;

							//pixelColor *= exposure_time;

							viewPlane.Pixels[pixel, currentLine] = pixelColor;
						}

						return new LineRenderUpdate( viewPlane.PixelHeight - currentLine - 1, viewPlane.GetLine( currentLine ) );
					}
				);
			}

			return tasks.ObserveResults();
		}

		private Vector3 GetDirection( double x, double y )
		{
			Vector3 direction = x * U + y * V - ViewPlaneDistance * W;
			return direction.Normalize();
		}
	}
}
